1   /*
2    * Copyright (c) 2003, 2007, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  package sun.print;
27  
28  import javax.print.attribute.*;
29  import javax.print.attribute.standard.*;
30  import javax.print.DocFlavor;
31  import javax.print.DocPrintJob;
32  import javax.print.PrintService;
33  import javax.print.ServiceUIFactory;
34  import java.util.ArrayList;
35  import java.util.HashMap;
36  import java.util.Locale;
37  import java.util.Date;
38  import java.util.Arrays;
39  import java.security.AccessController;
40  import java.security.PrivilegedActionException;
41  import java.security.PrivilegedExceptionAction;
42  import javax.print.event.PrintServiceAttributeListener;
43  
44  import java.net.URI;
45  import java.net.URISyntaxException;
46  import java.net.URL;
47  import java.net.HttpURLConnection;
48  import java.io.File;
49  import java.io.InputStream;
50  import java.io.OutputStream;
51  import java.io.OutputStreamWriter;
52  import java.io.DataInputStream;
53  import java.io.ByteArrayOutputStream;
54  import java.io.ByteArrayInputStream;
55  import java.io.BufferedReader;
56  import java.io.InputStreamReader;
57  import java.nio.charset.Charset;
58  
59  import java.util.Iterator;
60  import java.util.HashSet;
61  
62  
63  public class IPPPrintService implements PrintService, SunPrinterJobService {
64  
65      public static final boolean debugPrint;
66      private static final String debugPrefix = "IPPPrintService>> ";
67      protected static void debug_println(String str) {
68          if (debugPrint) {
69              System.out.println(str);
70          }
71      }
72  
73      private static final String FORCE_PIPE_PROP = "sun.print.ippdebug";
74  
75      static {
76          String debugStr =
77                  (String)java.security.AccessController.doPrivileged(
78                    new sun.security.action.GetPropertyAction(FORCE_PIPE_PROP));
79  
80          debugPrint = "true".equalsIgnoreCase(debugStr);
81      }
82  
83      private String printer;
84      private URI    myURI;
85      private URL    myURL;
86      transient private ServiceNotifier notifier = null;
87  
88      private static int MAXCOPIES = 1000;
89      private static short MAX_ATTRIBUTE_LENGTH = 255;
90  
91      private CUPSPrinter cps;
92      private HttpURLConnection urlConnection = null;
93      private DocFlavor[] supportedDocFlavors;
94      private Class[] supportedCats;
95      private MediaTray[] mediaTrays;
96      private MediaSizeName[] mediaSizeNames;
97      private CustomMediaSizeName[] customMediaSizeNames;
98      private int defaultMediaIndex;
99      private boolean isCupsPrinter;
100     private boolean init;
101     private Boolean isPS;
102     private HashMap getAttMap;
103     private boolean pngImagesAdded = false;
104     private boolean gifImagesAdded = false;
105     private boolean jpgImagesAdded = false;
106 
107 
108     /**
109      * IPP Status Codes
110      */
111     private static final byte STATUSCODE_SUCCESS = 0x00;
112 
113     /**
114      * IPP Group Tags.  Each tag is used once before the first attribute
115      * of that group.
116      */
117     // operation attributes group
118     private static final byte GRPTAG_OP_ATTRIBUTES = 0x01;
119     // job attributes group
120     private static final byte GRPTAG_JOB_ATTRIBUTES = 0x02;
121     // printer attributes group
122     private static final byte GRPTAG_PRINTER_ATTRIBUTES = 0x04;
123     // used as the last tag in an IPP message.
124     private static final byte GRPTAG_END_ATTRIBUTES = 0x03;
125 
126     /**
127      * IPP Operation codes
128      */
129     // gets the attributes for a printer
130     public static final String OP_GET_ATTRIBUTES = "000B";
131     // gets the default printer
132     public static final String OP_CUPS_GET_DEFAULT = "4001";
133     // gets the list of printers
134     public static final String OP_CUPS_GET_PRINTERS = "4002";
135 
136 
137     /**
138      * List of all PrintRequestAttributes.  This is used
139      * for looping through all the IPP attribute name.
140      */
141     private static Object[] printReqAttribDefault = {
142         Chromaticity.COLOR,
143         new Copies(1),
144         Fidelity.FIDELITY_FALSE,
145         Finishings.NONE,
146         //new JobHoldUntil(new Date()),
147         //new JobImpressions(0),
148         //JobImpressions,
149         //JobKOctets,
150         //JobMediaSheets,
151         new JobName("", Locale.getDefault()),
152         //JobPriority,
153         JobSheets.NONE,
154         (Media)MediaSizeName.NA_LETTER,
155         //MediaPrintableArea.class, // not an IPP attribute
156         //MultipleDocumentHandling.SINGLE_DOCUMENT,
157         new NumberUp(1),
158         OrientationRequested.PORTRAIT,
159         new PageRanges(1),
160         //PresentationDirection,
161                  // CUPS does not supply printer-resolution attribute
162         //new PrinterResolution(300, 300, PrinterResolution.DPI),
163         //PrintQuality.NORMAL,
164         new RequestingUserName("", Locale.getDefault()),
165         //SheetCollate.UNCOLLATED, //CUPS has no sheet collate?
166         Sides.ONE_SIDED,
167     };
168 
169 
170     /**
171      * List of all PrintServiceAttributes.  This is used
172      * for looping through all the IPP attribute name.
173      */
174     private static Object[][] serviceAttributes = {
175         {ColorSupported.class, "color-supported"},
176         {PagesPerMinute.class,  "pages-per-minute"},
177         {PagesPerMinuteColor.class, "pages-per-minute-color"},
178         {PDLOverrideSupported.class, "pdl-override-supported"},
179         {PrinterInfo.class, "printer-info"},
180         {PrinterIsAcceptingJobs.class, "printer-is-accepting-jobs"},
181         {PrinterLocation.class, "printer-location"},
182         {PrinterMakeAndModel.class, "printer-make-and-model"},
183         {PrinterMessageFromOperator.class, "printer-message-from-operator"},
184         {PrinterMoreInfo.class, "printer-more-info"},
185         {PrinterMoreInfoManufacturer.class, "printer-more-info-manufacturer"},
186         {PrinterName.class, "printer-name"},
187         {PrinterState.class, "printer-state"},
188         {PrinterStateReasons.class, "printer-state-reasons"},
189         {PrinterURI.class, "printer-uri"},
190         {QueuedJobCount.class, "queued-job-count"}
191     };
192 
193 
194     /**
195      * List of DocFlavors, grouped based on matching mime-type.
196      * NOTE: For any change in the predefined DocFlavors, it must be reflected
197      * here also.
198      */
199     // PDF DocFlavors
200     private static DocFlavor[] appPDF = {
201         DocFlavor.BYTE_ARRAY.PDF,
202         DocFlavor.INPUT_STREAM.PDF,
203         DocFlavor.URL.PDF
204     };
205 
206     // Postscript DocFlavors
207     private static DocFlavor[] appPostScript = {
208         DocFlavor.BYTE_ARRAY.POSTSCRIPT,
209         DocFlavor.INPUT_STREAM.POSTSCRIPT,
210         DocFlavor.URL.POSTSCRIPT
211     };
212 
213     // Autosense DocFlavors
214     private static DocFlavor[] appOctetStream = {
215         DocFlavor.BYTE_ARRAY.AUTOSENSE,
216         DocFlavor.INPUT_STREAM.AUTOSENSE,
217         DocFlavor.URL.AUTOSENSE
218     };
219 
220     // Text DocFlavors
221     private static DocFlavor[] textPlain = {
222         DocFlavor.BYTE_ARRAY.TEXT_PLAIN_UTF_8,
223         DocFlavor.BYTE_ARRAY.TEXT_PLAIN_UTF_16,
224         DocFlavor.BYTE_ARRAY.TEXT_PLAIN_UTF_16BE,
225         DocFlavor.BYTE_ARRAY.TEXT_PLAIN_UTF_16LE,
226         DocFlavor.BYTE_ARRAY.TEXT_PLAIN_US_ASCII,
227         DocFlavor.INPUT_STREAM.TEXT_PLAIN_UTF_8,
228         DocFlavor.INPUT_STREAM.TEXT_PLAIN_UTF_16,
229         DocFlavor.INPUT_STREAM.TEXT_PLAIN_UTF_16BE,
230         DocFlavor.INPUT_STREAM.TEXT_PLAIN_UTF_16LE,
231         DocFlavor.INPUT_STREAM.TEXT_PLAIN_US_ASCII,
232         DocFlavor.URL.TEXT_PLAIN_UTF_8,
233         DocFlavor.URL.TEXT_PLAIN_UTF_16,
234         DocFlavor.URL.TEXT_PLAIN_UTF_16BE,
235         DocFlavor.URL.TEXT_PLAIN_UTF_16LE,
236         DocFlavor.URL.TEXT_PLAIN_US_ASCII,
237         DocFlavor.CHAR_ARRAY.TEXT_PLAIN,
238         DocFlavor.STRING.TEXT_PLAIN,
239         DocFlavor.READER.TEXT_PLAIN
240     };
241 
242     private static DocFlavor[] textPlainHost = {
243         DocFlavor.BYTE_ARRAY.TEXT_PLAIN_HOST,
244         DocFlavor.INPUT_STREAM.TEXT_PLAIN_HOST,
245         DocFlavor.URL.TEXT_PLAIN_HOST
246     };
247 
248     // JPG DocFlavors
249     private static DocFlavor[] imageJPG = {
250         DocFlavor.BYTE_ARRAY.JPEG,
251         DocFlavor.INPUT_STREAM.JPEG,
252         DocFlavor.URL.JPEG
253     };
254 
255     // GIF DocFlavors
256     private static DocFlavor[] imageGIF = {
257         DocFlavor.BYTE_ARRAY.GIF,
258         DocFlavor.INPUT_STREAM.GIF,
259         DocFlavor.URL.GIF
260     };
261 
262     // PNG DocFlavors
263     private static DocFlavor[] imagePNG = {
264         DocFlavor.BYTE_ARRAY.PNG,
265         DocFlavor.INPUT_STREAM.PNG,
266         DocFlavor.URL.PNG
267     };
268 
269     // HTML DocFlavors
270     private  static DocFlavor[] textHtml = {
271         DocFlavor.BYTE_ARRAY.TEXT_HTML_UTF_8,
272         DocFlavor.BYTE_ARRAY.TEXT_HTML_UTF_16,
273         DocFlavor.BYTE_ARRAY.TEXT_HTML_UTF_16BE,
274         DocFlavor.BYTE_ARRAY.TEXT_HTML_UTF_16LE,
275         DocFlavor.BYTE_ARRAY.TEXT_HTML_US_ASCII,
276         DocFlavor.INPUT_STREAM.TEXT_HTML_UTF_8,
277         DocFlavor.INPUT_STREAM.TEXT_HTML_UTF_16,
278         DocFlavor.INPUT_STREAM.TEXT_HTML_UTF_16BE,
279         DocFlavor.INPUT_STREAM.TEXT_HTML_UTF_16LE,
280         DocFlavor.INPUT_STREAM.TEXT_HTML_US_ASCII,
281         DocFlavor.URL.TEXT_HTML_UTF_8,
282         DocFlavor.URL.TEXT_HTML_UTF_16,
283         DocFlavor.URL.TEXT_HTML_UTF_16BE,
284         DocFlavor.URL.TEXT_HTML_UTF_16LE,
285         DocFlavor.URL.TEXT_HTML_US_ASCII,
286         // These are not handled in UnixPrintJob so commenting these
287         // for now.
288         /*
289         DocFlavor.CHAR_ARRAY.TEXT_HTML,
290         DocFlavor.STRING.TEXT_HTML,
291         DocFlavor.READER.TEXT_HTML,
292         */
293     };
294 
295     private  static DocFlavor[] textHtmlHost = {
296         DocFlavor.BYTE_ARRAY.TEXT_HTML_HOST,
297         DocFlavor.INPUT_STREAM.TEXT_HTML_HOST,
298         DocFlavor.URL.TEXT_HTML_HOST,
299     };
300 
301 
302     // PCL DocFlavors
303     private static DocFlavor[] appPCL = {
304         DocFlavor.BYTE_ARRAY.PCL,
305         DocFlavor.INPUT_STREAM.PCL,
306         DocFlavor.URL.PCL
307     };
308 
309     // List of all DocFlavors, used in looping
310     // through all supported mime-types
311     private static Object[] allDocFlavors = {
312         appPDF, appPostScript, appOctetStream,
313         textPlain, imageJPG, imageGIF, imagePNG,
314         textHtml, appPCL,
315     };
316 
317 
318     IPPPrintService(String name, URL url) {
319         if ((name == null) || (url == null)){
320             throw new IllegalArgumentException("null uri or printer name");
321         }
322         printer = name;
323         supportedDocFlavors = null;
324         supportedCats = null;
325         mediaSizeNames = null;
326         customMediaSizeNames = null;
327         mediaTrays = null;
328         myURL = url;
329         cps = null;
330         isCupsPrinter = false;
331         init = false;
332         defaultMediaIndex = -1;
333 
334         String host = myURL.getHost();
335         if (host!=null && host.equals(CUPSPrinter.getServer())) {
336             isCupsPrinter = true;
337             try {
338                 myURI =  new URI("ipp://"+host+
339                                  "/printers/"+printer);
340                 debug_println(debugPrefix+"IPPPrintService myURI : "+myURI);
341             } catch (java.net.URISyntaxException e) {
342                 throw new IllegalArgumentException("invalid url");
343             }
344         }
345     }
346 
347 
348     IPPPrintService(String name, String uriStr, boolean isCups) {
349         if ((name == null) || (uriStr == null)){
350             throw new IllegalArgumentException("null uri or printer name");
351         }
352         printer = name;
353         supportedDocFlavors = null;
354         supportedCats = null;
355         mediaSizeNames = null;
356         customMediaSizeNames = null;
357         mediaTrays = null;
358         cps = null;
359         init = false;
360         defaultMediaIndex = -1;
361         try {
362             myURL =
363                 new URL(uriStr.replaceFirst("ipp", "http"));
364         } catch (Exception e) {
365             IPPPrintService.debug_println(debugPrefix+
366                                           " IPPPrintService, myURL="+
367                                           myURL+" Exception= "+
368                                           e);
369         }
370 
371         isCupsPrinter = isCups;
372         try {
373             myURI =  new URI(uriStr);
374             debug_println(debugPrefix+"IPPPrintService myURI : "+myURI);
375         } catch (java.net.URISyntaxException e) {
376             throw new IllegalArgumentException("invalid uri");
377         }
378     }
379 
380 
381     /*
382      * Initialize mediaSizeNames, mediaTrays and other attributes.
383      * Media size/trays are initialized to non-null values, may be 0-length
384      * array.
385      * NOTE: Must be called from a synchronized block only.
386      */
387     private void initAttributes() {
388         if (!init) {
389             // init customMediaSizeNames
390             customMediaSizeNames = new CustomMediaSizeName[0];
391 
392             if ((urlConnection = getIPPConnection(myURL)) == null) {
393                 mediaSizeNames = new MediaSizeName[0];
394                 mediaTrays = new MediaTray[0];
395                 debug_println(debugPrefix+"initAttributes, NULL urlConnection ");
396                 init = true;
397                 return;
398             }
399 
400             // get all supported attributes through IPP
401             opGetAttributes();
402 
403             if (isCupsPrinter) {
404                 // note, it is possible to query media in CUPS using IPP
405                 // right now we always get it from PPD.
406                 // maybe use "&& (usePPD)" later?
407                 // Another reason why we use PPD is because
408                 // IPP currently does not support it but PPD does.
409 
410                 try {
411                     cps = new CUPSPrinter(printer);
412                     mediaSizeNames = cps.getMediaSizeNames();
413                     mediaTrays = cps.getMediaTrays();
414                     customMediaSizeNames = cps.getCustomMediaSizeNames();
415                     urlConnection.disconnect();
416                     init = true;
417                     return;
418                 } catch (Exception e) {
419                     IPPPrintService.debug_println(debugPrefix+
420                                        "initAttributes, error creating CUPSPrinter e="+e);
421                 }
422             }
423 
424             // use IPP to get all media,
425             Media[] allMedia = (Media[])getSupportedMedia();
426             ArrayList sizeList = new ArrayList();
427             ArrayList trayList = new ArrayList();
428             for (int i=0; i<allMedia.length; i++) {
429                 if (allMedia[i] instanceof MediaSizeName) {
430                     sizeList.add(allMedia[i]);
431                 } else if (allMedia[i] instanceof MediaTray) {
432                     trayList.add(allMedia[i]);
433                 }
434             }
435 
436             if (sizeList != null) {
437                 mediaSizeNames = new MediaSizeName[sizeList.size()];
438                 mediaSizeNames = (MediaSizeName[])sizeList.toArray(
439                                                        mediaSizeNames);
440             }
441             if (trayList != null) {
442                 mediaTrays = new MediaTray[trayList.size()];
443                 mediaTrays = (MediaTray[])trayList.toArray(
444                                                            mediaTrays);
445             }
446             urlConnection.disconnect();
447 
448             init = true;
449         }
450     }
451 
452 
453     public DocPrintJob createPrintJob() {
454         SecurityManager security = System.getSecurityManager();
455         if (security != null) {
456             security.checkPrintJobAccess();
457         }
458         // REMIND: create IPPPrintJob
459         return new UnixPrintJob(this);
460     }
461 
462 
463     public synchronized Object
464         getSupportedAttributeValues(Class<? extends Attribute> category,
465                                     DocFlavor flavor,
466                                     AttributeSet attributes)
467     {
468         if (category == null) {
469             throw new NullPointerException("null category");
470         }
471         if (!Attribute.class.isAssignableFrom(category)) {
472             throw new IllegalArgumentException(category +
473                                  " does not implement Attribute");
474         }
475         if (flavor != null) {
476             if (!isDocFlavorSupported(flavor)) {
477                 throw new IllegalArgumentException(flavor +
478                                                " is an unsupported flavor");
479             } else if (isAutoSense(flavor)) {
480                 return null;
481             }
482 
483         }
484 
485         if (!isAttributeCategorySupported(category)) {
486             return null;
487         }
488 
489         /* Test if the flavor is compatible with the attributes */
490         if (!isDestinationSupported(flavor, attributes)) {
491             return null;
492         }
493 
494         initAttributes();
495 
496         /* Test if the flavor is compatible with the category */
497         if ((category == Copies.class) ||
498             (category == CopiesSupported.class)) {
499             if (flavor == null ||
500                 !(flavor.equals(DocFlavor.INPUT_STREAM.POSTSCRIPT) ||
501                   flavor.equals(DocFlavor.URL.POSTSCRIPT) ||
502                   flavor.equals(DocFlavor.BYTE_ARRAY.POSTSCRIPT))) {
503                 CopiesSupported cs = new CopiesSupported(1, MAXCOPIES);
504                 AttributeClass attribClass = (getAttMap != null) ?
505                     (AttributeClass)getAttMap.get(cs.getName()) : null;
506                 if (attribClass != null) {
507                     int[] range = attribClass.getIntRangeValue();
508                     cs = new CopiesSupported(range[0], range[1]);
509                 }
510                 return cs;
511             } else {
512                 return null;
513             }
514         } else  if (category == Chromaticity.class) {
515             if (flavor == null ||
516                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
517                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE) ||
518                 !isIPPSupportedImages(flavor.getMimeType())) {
519                 Chromaticity[]arr = new Chromaticity[1];
520                 arr[0] = Chromaticity.COLOR;
521                 return (arr);
522             } else {
523                 return null;
524             }
525         } else if (category == Destination.class) {
526             if (flavor == null ||
527                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
528                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE)) {
529                 try {
530                     return new Destination((new File("out.ps")).toURI());
531                 } catch (SecurityException se) {
532                     try {
533                         return new Destination(new URI("file:out.ps"));
534                     } catch (URISyntaxException e) {
535                         return null;
536                     }
537                 }
538             }
539             return null;
540         } else if (category == Fidelity.class) {
541             Fidelity []arr = new Fidelity[2];
542             arr[0] = Fidelity.FIDELITY_FALSE;
543             arr[1] = Fidelity.FIDELITY_TRUE;
544             return arr;
545         } else if (category == Finishings.class) {
546             AttributeClass attribClass = (getAttMap != null) ?
547                 (AttributeClass)getAttMap.get("finishings-supported")
548                 : null;
549             if (attribClass != null) {
550                 int[] finArray = attribClass.getArrayOfIntValues();
551                 if ((finArray != null) && (finArray.length > 0)) {
552                     Finishings[] finSup = new Finishings[finArray.length];
553                     for (int i=0; i<finArray.length; i++) {
554                         finSup[i] = Finishings.NONE;
555                         Finishings[] fAll = (Finishings[])
556                             (new ExtFinishing(100)).getAll();
557                         for (int j=0; j<fAll.length; j++) {
558                             if (finArray[i] == fAll[j].getValue()) {
559                                 finSup[i] = fAll[j];
560                                 break;
561                             }
562                         }
563                     }
564                     return finSup;
565                 }
566             }
567         } else if (category == JobName.class) {
568             return new JobName("Java Printing", null);
569         } else if (category == JobSheets.class) {
570             JobSheets arr[] = new JobSheets[2];
571             arr[0] = JobSheets.NONE;
572             arr[1] = JobSheets.STANDARD;
573             return arr;
574 
575         } else if (category == Media.class) {
576             Media[] allMedia = new Media[mediaSizeNames.length+
577                                         mediaTrays.length];
578 
579             for (int i=0; i<mediaSizeNames.length; i++) {
580                 allMedia[i] = mediaSizeNames[i];
581             }
582 
583             for (int i=0; i<mediaTrays.length; i++) {
584                 allMedia[i+mediaSizeNames.length] = mediaTrays[i];
585             }
586 
587             if (allMedia.length == 0) {
588                 allMedia = new Media[1];
589                 allMedia[0] = (Media)getDefaultAttributeValue(Media.class);
590             }
591 
592             return allMedia;
593         } else if (category == MediaPrintableArea.class) {
594             MediaPrintableArea[] mpas = null;
595             if (cps != null) {
596                 mpas = cps.getMediaPrintableArea();
597             }
598 
599             if (mpas == null) {
600                 mpas = new MediaPrintableArea[1];
601                 mpas[0] = (MediaPrintableArea)
602                     getDefaultAttributeValue(MediaPrintableArea.class);
603             }
604 
605             if ((attributes == null) || (attributes.size() == 0)) {
606                 ArrayList<MediaPrintableArea> printableList =
607                                        new ArrayList<MediaPrintableArea>();
608 
609                 for (int i=0; i<mpas.length; i++) {
610                     if (mpas[i] != null) {
611                         printableList.add(mpas[i]);
612                     }
613                 }
614                 if (printableList.size() > 0) {
615                     mpas  = new MediaPrintableArea[printableList.size()];
616                     printableList.toArray(mpas);
617                 }
618                 return mpas;
619             }
620 
621             int match = -1;
622             Media media = (Media)attributes.get(Media.class);
623             if (media != null && media instanceof MediaSizeName) {
624                 MediaSizeName msn = (MediaSizeName)media;
625 
626                 // case when no supported mediasizenames are reported
627                 // check given media against the default
628                 if (mediaSizeNames.length == 0 &&
629                     msn.equals(getDefaultAttributeValue(Media.class))) {
630                     //default printable area is that of default mediasize
631                     return mpas;
632                 }
633 
634                 for (int i=0; i<mediaSizeNames.length; i++) {
635                     if (msn.equals(mediaSizeNames[i])) {
636                         match = i;
637                     }
638                 }
639             }
640 
641             if (match == -1) {
642                 return null;
643             } else {
644                 MediaPrintableArea []arr = new MediaPrintableArea[1];
645                 arr[0] = mpas[match];
646                 return arr;
647             }
648         } else if (category == NumberUp.class) {
649             AttributeClass attribClass = (getAttMap != null) ?
650                 (AttributeClass)getAttMap.get("number-up-supported") : null;
651             if (attribClass != null) {
652                 int[] values = attribClass.getArrayOfIntValues();
653                 if (values != null) {
654                     NumberUp[] nUp = new NumberUp[values.length];
655                     for (int i=0; i<values.length; i++) {
656                         nUp[i] = new NumberUp(values[i]);
657                     }
658                     return nUp;
659                 } else {
660                     return null;
661                 }
662             }
663         } else if (category == OrientationRequested.class) {
664             if ((flavor != null) &&
665                 (flavor.equals(DocFlavor.INPUT_STREAM.POSTSCRIPT) ||
666                  flavor.equals(DocFlavor.URL.POSTSCRIPT) ||
667                  flavor.equals(DocFlavor.BYTE_ARRAY.POSTSCRIPT))) {
668                 return null;
669             }
670 
671             boolean revPort = false;
672             OrientationRequested[] orientSup = null;
673 
674             AttributeClass attribClass = (getAttMap != null) ?
675               (AttributeClass)getAttMap.get("orientation-requested-supported")
676                 : null;
677             if (attribClass != null) {
678                 int[] orientArray = attribClass.getArrayOfIntValues();
679                 if ((orientArray != null) && (orientArray.length > 0)) {
680                     orientSup =
681                         new OrientationRequested[orientArray.length];
682                     for (int i=0; i<orientArray.length; i++) {
683                         switch (orientArray[i]) {
684                         default:
685                         case 3 :
686                             orientSup[i] = OrientationRequested.PORTRAIT;
687                             break;
688                         case 4:
689                             orientSup[i] = OrientationRequested.LANDSCAPE;
690                             break;
691                         case 5:
692                             orientSup[i] =
693                                 OrientationRequested.REVERSE_LANDSCAPE;
694                             break;
695                         case 6:
696                             orientSup[i] =
697                                 OrientationRequested.REVERSE_PORTRAIT;
698                             revPort = true;
699                             break;
700                         }
701                     }
702                 }
703             }
704             if (flavor == null ||
705                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
706                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE)) {
707 
708                 if (revPort && flavor == null) {
709                     OrientationRequested []orSup = new OrientationRequested[4];
710                     orSup[0] = OrientationRequested.PORTRAIT;
711                     orSup[1] = OrientationRequested.LANDSCAPE;
712                     orSup[2] = OrientationRequested.REVERSE_LANDSCAPE;
713                     orSup[3] = OrientationRequested.REVERSE_PORTRAIT;
714                     return orSup;
715                 } else {
716                     OrientationRequested []orSup = new OrientationRequested[3];
717                     orSup[0] = OrientationRequested.PORTRAIT;
718                     orSup[1] = OrientationRequested.LANDSCAPE;
719                     orSup[2] = OrientationRequested.REVERSE_LANDSCAPE;
720                     return orSup;
721                 }
722             } else {
723                 return orientSup;
724             }
725         } else if (category == PageRanges.class) {
726            if (flavor == null ||
727                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
728                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE)) {
729                 PageRanges []arr = new PageRanges[1];
730                 arr[0] = new PageRanges(1, Integer.MAX_VALUE);
731                 return arr;
732             } else {
733                 // Returning null as this is not yet supported in UnixPrintJob.
734                 return null;
735             }
736         } else if (category == RequestingUserName.class) {
737             String userName = "";
738             try {
739               userName = System.getProperty("user.name", "");
740             } catch (SecurityException se) {
741             }
742             return new RequestingUserName(userName, null);
743         } else if (category == Sides.class) {
744             // The printer takes care of Sides so if short-edge
745             // is chosen in a job, the rotation is done by the printer.
746             // Orientation is rotated by emulation if pageable
747             // or printable so if the document is in Landscape, this may
748             // result in double rotation.
749             AttributeClass attribClass = (getAttMap != null) ?
750                 (AttributeClass)getAttMap.get("sides-supported")
751                 : null;
752             if (attribClass != null) {
753                 String[] sidesArray = attribClass.getArrayOfStringValues();
754                 if ((sidesArray != null) && (sidesArray.length > 0)) {
755                     Sides[] sidesSup = new Sides[sidesArray.length];
756                     for (int i=0; i<sidesArray.length; i++) {
757                         if (sidesArray[i].endsWith("long-edge")) {
758                             sidesSup[i] = Sides.TWO_SIDED_LONG_EDGE;
759                         } else if (sidesArray[i].endsWith("short-edge")) {
760                             sidesSup[i] = Sides.TWO_SIDED_SHORT_EDGE;
761                         } else {
762                             sidesSup[i] = Sides.ONE_SIDED;
763                         }
764                     }
765                     return sidesSup;
766                 }
767             }
768         }
769 
770         return null;
771     }
772 
773     //This class is for getting all pre-defined Finishings
774     private class ExtFinishing extends Finishings {
775         ExtFinishing(int value) {
776             super(100); // 100 to avoid any conflicts with predefined values.
777         }
778 
779         EnumSyntax[] getAll() {
780             EnumSyntax[] es = super.getEnumValueTable();
781             return es;
782         }
783     }
784 
785 
786     public AttributeSet getUnsupportedAttributes(DocFlavor flavor,
787                                                  AttributeSet attributes) {
788         if (flavor != null && !isDocFlavorSupported(flavor)) {
789             throw new IllegalArgumentException("flavor " + flavor +
790                                                "is not supported");
791         }
792 
793         if (attributes == null) {
794             return null;
795         }
796 
797         Attribute attr;
798         AttributeSet unsupp = new HashAttributeSet();
799         Attribute []attrs = attributes.toArray();
800         for (int i=0; i<attrs.length; i++) {
801             try {
802                 attr = attrs[i];
803                 if (!isAttributeCategorySupported(attr.getCategory())) {
804                     unsupp.add(attr);
805                 } else if (!isAttributeValueSupported(attr, flavor,
806                                                       attributes)) {
807                     unsupp.add(attr);
808                 }
809             } catch (ClassCastException e) {
810             }
811         }
812         if (unsupp.isEmpty()) {
813             return null;
814         } else {
815             return unsupp;
816         }
817     }
818 
819 
820     public synchronized DocFlavor[] getSupportedDocFlavors() {
821 
822         if (supportedDocFlavors != null) {
823             int len = supportedDocFlavors.length;
824                 DocFlavor[] copyflavors = new DocFlavor[len];
825                 System.arraycopy(supportedDocFlavors, 0, copyflavors, 0, len);
826                 return copyflavors;
827         }
828         initAttributes();
829 
830         if ((getAttMap != null) &&
831             getAttMap.containsKey("document-format-supported")) {
832 
833             AttributeClass attribClass =
834                 (AttributeClass)getAttMap.get("document-format-supported");
835             if (attribClass != null) {
836                 String mimeType;
837                 boolean psSupported = false;
838                 String[] docFlavors = attribClass.getArrayOfStringValues();
839                 DocFlavor[] flavors;
840                 HashSet docList = new HashSet();
841                 int j;
842                 String hostEnc = DocFlavor.hostEncoding.
843                     toLowerCase(Locale.ENGLISH);
844                 boolean addHostEncoding = !hostEnc.equals("utf-8") &&
845                     !hostEnc.equals("utf-16") && !hostEnc.equals("utf-16be") &&
846                     !hostEnc.equals("utf-16le") && !hostEnc.equals("us-ascii");
847 
848                 for (int i = 0; i < docFlavors.length; i++) {
849                     for (j=0; j<allDocFlavors.length; j++) {
850                         flavors = (DocFlavor[])allDocFlavors[j];
851 
852                         mimeType = flavors[0].getMimeType();
853                         if (mimeType.startsWith(docFlavors[i])) {
854 
855                             docList.addAll(Arrays.asList(flavors));
856 
857                             if (mimeType.equals("text/plain") &&
858                                 addHostEncoding) {
859                                 docList.add(Arrays.asList(textPlainHost));
860                             } else if (mimeType.equals("text/html") &&
861                                        addHostEncoding) {
862                                 docList.add(Arrays.asList(textHtmlHost));
863                             } else if (mimeType.equals("image/png")) {
864                                 pngImagesAdded = true;
865                             } else if (mimeType.equals("image/gif")) {
866                                 gifImagesAdded = true;
867                             } else if (mimeType.equals("image/jpeg")) {
868                                 jpgImagesAdded = true;
869                             } else if (mimeType.indexOf("postscript") != -1) {
870                                 psSupported = true;
871                             }
872                             break;
873                         }
874                     }
875 
876                     // Not added? Create new DocFlavors
877                     if (j == allDocFlavors.length) {
878                         //  make new DocFlavors
879                         docList.add(new DocFlavor.BYTE_ARRAY(docFlavors[i]));
880                         docList.add(new DocFlavor.INPUT_STREAM(docFlavors[i]));
881                         docList.add(new DocFlavor.URL(docFlavors[i]));
882                     }
883                 }
884 
885                 // check if we need to add image DocFlavors
886                 // and Pageable/Printable flavors
887                 if (psSupported || isCupsPrinter) {
888                     /*
889                      Always add Pageable and Printable for CUPS
890                      since it uses Filters to convert from Postscript
891                      to device printer language.
892                     */
893                     docList.add(DocFlavor.SERVICE_FORMATTED.PAGEABLE);
894                     docList.add(DocFlavor.SERVICE_FORMATTED.PRINTABLE);
895 
896                     docList.addAll(Arrays.asList(imageJPG));
897                     docList.addAll(Arrays.asList(imagePNG));
898                     docList.addAll(Arrays.asList(imageGIF));
899                 }
900                 supportedDocFlavors = new DocFlavor[docList.size()];
901                 docList.toArray(supportedDocFlavors);
902                 int len = supportedDocFlavors.length;
903                 DocFlavor[] copyflavors = new DocFlavor[len];
904                 System.arraycopy(supportedDocFlavors, 0, copyflavors, 0, len);
905                 return copyflavors;
906             }
907         }
908         return null;
909     }
910 
911 
912     public boolean isDocFlavorSupported(DocFlavor flavor) {
913         if (supportedDocFlavors == null) {
914             getSupportedDocFlavors();
915         }
916         if (supportedDocFlavors != null) {
917             for (int f=0; f<supportedDocFlavors.length; f++) {
918                 if (flavor.equals(supportedDocFlavors[f])) {
919                     return true;
920                 }
921             }
922         }
923         return false;
924     }
925 
926 
927     /**
928      * Finds matching CustomMediaSizeName of given media.
929      */
930     public CustomMediaSizeName findCustomMedia(MediaSizeName media) {
931         if (customMediaSizeNames == null) {
932             return null;
933         }
934         for (int i=0; i< customMediaSizeNames.length; i++) {
935             CustomMediaSizeName custom =
936                 (CustomMediaSizeName)customMediaSizeNames[i];
937             MediaSizeName msn = custom.getStandardMedia();
938             if (media.equals(msn)) {
939                 return customMediaSizeNames[i];
940             }
941         }
942         return null;
943     }
944 
945 
946     /**
947      * Returns the matching standard Media using string comparison of names.
948      */
949     private Media getIPPMedia(String mediaName) {
950         CustomMediaSizeName sampleSize = new CustomMediaSizeName("sample", "",
951                                                                  0, 0);
952         Media[] sizes = sampleSize.getSuperEnumTable();
953         for (int i=0; i<sizes.length; i++) {
954             if (mediaName.equals(""+sizes[i])) {
955                 return sizes[i];
956             }
957         }
958         CustomMediaTray sampleTray = new CustomMediaTray("sample", "");
959         Media[] trays = sampleTray.getSuperEnumTable();
960         for (int i=0; i<trays.length; i++) {
961             if (mediaName.equals(""+trays[i])) {
962                 return trays[i];
963             }
964         }
965         return null;
966     }
967 
968     private Media[] getSupportedMedia() {
969         if ((getAttMap != null) &&
970             getAttMap.containsKey("media-supported")) {
971 
972             AttributeClass attribClass =
973                 (AttributeClass)getAttMap.get("media-supported");
974 
975             if (attribClass != null) {
976                 String[] mediaVals = attribClass.getArrayOfStringValues();
977                 Media msn;
978                 Media[] mediaNames =
979                     new Media[mediaVals.length];
980                 for (int i=0; i<mediaVals.length; i++) {
981                     msn = getIPPMedia(mediaVals[i]);
982                     //REMIND: if null, create custom?
983                     mediaNames[i] = msn;
984                 }
985                 return mediaNames;
986             }
987         }
988         return new Media[0];
989     }
990 
991 
992     public synchronized Class[] getSupportedAttributeCategories() {
993         if (supportedCats != null) {
994             return supportedCats;
995         }
996 
997         initAttributes();
998 
999         ArrayList catList = new ArrayList();
1000         Class cl;
1001 
1002         for (int i=0; i < printReqAttribDefault.length; i++) {
1003             PrintRequestAttribute pra =
1004                 (PrintRequestAttribute)printReqAttribDefault[i];
1005             if (getAttMap != null &&
1006                 getAttMap.containsKey(pra.getName()+"-supported")) {
1007                 cl = pra.getCategory();
1008                 catList.add(cl);
1009             }
1010         }
1011 
1012         // Some IPP printers like lexc710 do not have list of supported media
1013         // but CUPS can get the media from PPD, so we still report as
1014         // supported category.
1015         if (isCupsPrinter) {
1016             if (!catList.contains(Media.class)) {
1017                 catList.add(Media.class);
1018             }
1019 
1020             // Always add MediaPrintable for cups,
1021             // because we can get it from PPD.
1022             catList.add(MediaPrintableArea.class);
1023 
1024             // this is already supported in UnixPrintJob
1025             catList.add(Destination.class);
1026         }
1027 
1028         // With the assumption that  Chromaticity is equivalent to
1029         // ColorSupported.
1030         if (getAttMap != null && getAttMap.containsKey("color-supported")) {
1031             catList.add(Chromaticity.class);
1032         }
1033         supportedCats = new Class[catList.size()];
1034         catList.toArray(supportedCats);
1035         return supportedCats;
1036     }
1037 
1038 
1039     public boolean
1040         isAttributeCategorySupported(Class<? extends Attribute> category)
1041     {
1042         if (category == null) {
1043             throw new NullPointerException("null category");
1044         }
1045         if (!(Attribute.class.isAssignableFrom(category))) {
1046             throw new IllegalArgumentException(category +
1047                                              " is not an Attribute");
1048         }
1049 
1050         if (supportedCats == null) {
1051             getSupportedAttributeCategories();
1052         }
1053 
1054         // It is safe to assume that Orientation is always supported
1055         // and even if CUPS or an IPP device reports it as not,
1056         // our renderer can do portrait, landscape and
1057         // reverse landscape.
1058         if (category == OrientationRequested.class) {
1059             return true;
1060         }
1061 
1062         for (int i=0;i<supportedCats.length;i++) {
1063             if (category == supportedCats[i]) {
1064                 return true;
1065             }
1066         }
1067 
1068         return false;
1069     }
1070 
1071 
1072     public synchronized <T extends PrintServiceAttribute>
1073         T getAttribute(Class<T> category)
1074     {
1075         if (category == null) {
1076             throw new NullPointerException("category");
1077         }
1078         if (!(PrintServiceAttribute.class.isAssignableFrom(category))) {
1079             throw new IllegalArgumentException("Not a PrintServiceAttribute");
1080         }
1081 
1082         initAttributes();
1083 
1084         if (category == PrinterName.class) {
1085             return (T)(new PrinterName(printer, null));
1086         } else if (category == QueuedJobCount.class) {
1087             QueuedJobCount qjc = new QueuedJobCount(0);
1088             AttributeClass ac = (getAttMap != null) ?
1089                 (AttributeClass)getAttMap.get(qjc.getName())
1090                 : null;
1091             if (ac != null) {
1092                 qjc = new QueuedJobCount(ac.getIntValue());
1093             }
1094             return (T)qjc;
1095         } else if (category == PrinterIsAcceptingJobs.class) {
1096             PrinterIsAcceptingJobs accJob =
1097                 PrinterIsAcceptingJobs.ACCEPTING_JOBS;
1098             AttributeClass ac = (getAttMap != null) ?
1099                 (AttributeClass)getAttMap.get(accJob.getName())
1100                 : null;
1101             if ((ac != null) && (ac.getByteValue() == 0)) {
1102                 accJob = PrinterIsAcceptingJobs.NOT_ACCEPTING_JOBS;
1103             }
1104             return (T)accJob;
1105         } else if (category == ColorSupported.class) {
1106             ColorSupported cs = ColorSupported.SUPPORTED;
1107             AttributeClass ac = (getAttMap != null) ?
1108                 (AttributeClass)getAttMap.get(cs.getName())
1109                 : null;
1110             if ((ac != null) && (ac.getByteValue() == 0)) {
1111                 cs = ColorSupported.NOT_SUPPORTED;
1112             }
1113             return (T)cs;
1114         } else if (category == PDLOverrideSupported.class) {
1115 
1116             if (isCupsPrinter) {
1117                 // Documented: For CUPS this will always be false
1118                 return (T)PDLOverrideSupported.NOT_ATTEMPTED;
1119             } else {
1120                 // REMIND: check attribute values
1121                 return (T)PDLOverrideSupported.NOT_ATTEMPTED;
1122             }
1123         } else {
1124             return null;
1125         }
1126     }
1127 
1128 
1129     public synchronized PrintServiceAttributeSet getAttributes() {
1130         // update getAttMap by sending again get-attributes IPP request
1131         init = false;
1132         initAttributes();
1133 
1134         HashPrintServiceAttributeSet attrs =
1135             new HashPrintServiceAttributeSet();
1136 
1137         for (int i=0; i < serviceAttributes.length; i++) {
1138             String name = (String)serviceAttributes[i][1];
1139             if (getAttMap != null && getAttMap.containsKey(name)) {
1140                 Class c = (Class)serviceAttributes[i][0];
1141                 PrintServiceAttribute psa = getAttribute(c);
1142                 if (psa != null) {
1143                     attrs.add(psa);
1144                 }
1145             }
1146         }
1147         return AttributeSetUtilities.unmodifiableView(attrs);
1148     }
1149 
1150     public boolean isIPPSupportedImages(String mimeType) {
1151         if (supportedDocFlavors == null) {
1152             getSupportedDocFlavors();
1153         }
1154 
1155         if (mimeType.equals("image/png") && pngImagesAdded) {
1156             return true;
1157         } else if (mimeType.equals("image/gif") && gifImagesAdded) {
1158             return true;
1159         } else if (mimeType.equals("image/jpeg") && jpgImagesAdded) {
1160             return true;
1161         }
1162 
1163         return false;
1164     }
1165 
1166 
1167     private boolean isSupportedCopies(Copies copies) {
1168         CopiesSupported cs = (CopiesSupported)
1169             getSupportedAttributeValues(Copies.class, null, null);
1170         int[][] members = cs.getMembers();
1171         int min, max;
1172         if ((members.length > 0) && (members[0].length > 0)) {
1173             min = members[0][0];
1174             max = members[0][1];
1175         } else {
1176             min = 1;
1177             max = MAXCOPIES;
1178         }
1179 
1180         int value = copies.getValue();
1181         return (value >= min && value <= max);
1182     }
1183 
1184     private boolean isAutoSense(DocFlavor flavor) {
1185         if (flavor.equals(DocFlavor.BYTE_ARRAY.AUTOSENSE) ||
1186             flavor.equals(DocFlavor.INPUT_STREAM.AUTOSENSE) ||
1187             flavor.equals(DocFlavor.URL.AUTOSENSE)) {
1188             return true;
1189         }
1190         else {
1191             return false;
1192         }
1193     }
1194 
1195     private synchronized boolean isSupportedMediaTray(MediaTray msn) {
1196         initAttributes();
1197 
1198         if (mediaTrays != null) {
1199             for (int i=0; i<mediaTrays.length; i++) {
1200                if (msn.equals(mediaTrays[i])) {
1201                     return true;
1202                 }
1203             }
1204         }
1205         return false;
1206     }
1207 
1208     private synchronized boolean isSupportedMedia(MediaSizeName msn) {
1209         initAttributes();
1210 
1211         if (msn.equals((Media)getDefaultAttributeValue(Media.class))) {
1212             return true;
1213         }
1214         for (int i=0; i<mediaSizeNames.length; i++) {
1215             debug_println(debugPrefix+"isSupportedMedia, mediaSizeNames[i] "+mediaSizeNames[i]);
1216             if (msn.equals(mediaSizeNames[i])) {
1217                 return true;
1218             }
1219         }
1220         return false;
1221     }
1222 
1223     /* Return false if flavor is not null, pageable, nor printable and
1224      * Destination is part of attributes.
1225      */
1226     private boolean
1227         isDestinationSupported(DocFlavor flavor, AttributeSet attributes) {
1228 
1229             if ((attributes != null) &&
1230                     (attributes.get(Destination.class) != null) &&
1231                     !(flavor == null ||
1232                       flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
1233                       flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE))) {
1234                 return false;
1235             }
1236             return true;
1237     }
1238 
1239 
1240     public boolean isAttributeValueSupported(Attribute attr,
1241                                              DocFlavor flavor,
1242                                              AttributeSet attributes) {
1243         if (attr == null) {
1244             throw new NullPointerException("null attribute");
1245         }
1246         if (flavor != null) {
1247             if (!isDocFlavorSupported(flavor)) {
1248                 throw new IllegalArgumentException(flavor +
1249                                                " is an unsupported flavor");
1250             } else if (isAutoSense(flavor)) {
1251                 return false;
1252             }
1253         }
1254         Class category = attr.getCategory();
1255         if (!isAttributeCategorySupported(category)) {
1256             return false;
1257         }
1258 
1259         /* Test if the flavor is compatible with the attributes */
1260         if (!isDestinationSupported(flavor, attributes)) {
1261             return false;
1262         }
1263 
1264         /* Test if the flavor is compatible with the category */
1265         if (attr.getCategory() == Chromaticity.class) {
1266             if ((flavor == null) ||
1267                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
1268                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE) ||
1269                 !isIPPSupportedImages(flavor.getMimeType())) {
1270                 return attr == Chromaticity.COLOR;
1271             } else {
1272                 return false;
1273             }
1274         } else if (attr.getCategory() == Copies.class) {
1275             return (flavor == null ||
1276                    !(flavor.equals(DocFlavor.INPUT_STREAM.POSTSCRIPT) ||
1277                    flavor.equals(DocFlavor.URL.POSTSCRIPT) ||
1278                    flavor.equals(DocFlavor.BYTE_ARRAY.POSTSCRIPT))) &&
1279                 isSupportedCopies((Copies)attr);
1280 
1281         } else if (attr.getCategory() == Destination.class) {
1282             if (flavor == null ||
1283                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
1284                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE)) {
1285                 URI uri = ((Destination)attr).getURI();
1286                 if ("file".equals(uri.getScheme()) &&
1287                     !(uri.getSchemeSpecificPart().equals(""))) {
1288                     return true;
1289                 }
1290             }
1291             return false;
1292         } else if (attr.getCategory() == Media.class) {
1293             if (attr instanceof MediaSizeName) {
1294                 return isSupportedMedia((MediaSizeName)attr);
1295             }
1296             if (attr instanceof MediaTray) {
1297                 return isSupportedMediaTray((MediaTray)attr);
1298             }
1299         } else if (attr.getCategory() == PageRanges.class) {
1300             if (flavor != null &&
1301                 !(flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
1302                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE))) {
1303                 return false;
1304             }
1305         } else if (attr.getCategory() == SheetCollate.class) {
1306             if (flavor != null &&
1307                 !(flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE) ||
1308                 flavor.equals(DocFlavor.SERVICE_FORMATTED.PRINTABLE))) {
1309                 return false;
1310             }
1311         } else if (attr.getCategory() == Sides.class) {
1312             Sides[] sidesArray = (Sides[])getSupportedAttributeValues(
1313                                           Sides.class,
1314                                           flavor,
1315                                           attributes);
1316 
1317             if (sidesArray != null) {
1318                 for (int i=0; i<sidesArray.length; i++) {
1319                     if (sidesArray[i] == (Sides)attr) {
1320                         return true;
1321                     }
1322                 }
1323             }
1324             return false;
1325         } else if (attr.getCategory() == OrientationRequested.class) {
1326             OrientationRequested[] orientArray =
1327                 (OrientationRequested[])getSupportedAttributeValues(
1328                                           OrientationRequested.class,
1329                                           flavor,
1330                                           attributes);
1331 
1332             if (orientArray != null) {
1333                 for (int i=0; i<orientArray.length; i++) {
1334                     if (orientArray[i] == (OrientationRequested)attr) {
1335                         return true;
1336                     }
1337                 }
1338             }
1339             return false;
1340         }
1341         return true;
1342     }
1343 
1344 
1345     public synchronized Object
1346         getDefaultAttributeValue(Class<? extends Attribute> category)
1347     {
1348         if (category == null) {
1349             throw new NullPointerException("null category");
1350         }
1351         if (!Attribute.class.isAssignableFrom(category)) {
1352             throw new IllegalArgumentException(category +
1353                                              " is not an Attribute");
1354         }
1355         if (!isAttributeCategorySupported(category)) {
1356             return null;
1357         }
1358 
1359         initAttributes();
1360 
1361         String catName = null;
1362         for (int i=0; i < printReqAttribDefault.length; i++) {
1363             PrintRequestAttribute pra =
1364                 (PrintRequestAttribute)printReqAttribDefault[i];
1365             if (pra.getCategory() == category) {
1366                 catName = pra.getName();
1367                 break;
1368             }
1369         }
1370         String attribName = catName+"-default";
1371         AttributeClass attribClass = (getAttMap != null) ?
1372                 (AttributeClass)getAttMap.get(attribName) : null;
1373 
1374         if (category == Copies.class) {
1375             if (attribClass != null) {
1376                 return new Copies(attribClass.getIntValue());
1377             } else {
1378                 return new Copies(1);
1379             }
1380         } else if (category == Chromaticity.class) {
1381             return Chromaticity.COLOR;
1382         } else if (category == Destination.class) {
1383             try {
1384                 return new Destination((new File("out.ps")).toURI());
1385             } catch (SecurityException se) {
1386                 try {
1387                     return new Destination(new URI("file:out.ps"));
1388                 } catch (URISyntaxException e) {
1389                     return null;
1390                 }
1391             }
1392         } else if (category == Fidelity.class) {
1393             return Fidelity.FIDELITY_FALSE;
1394         } else if (category == Finishings.class) {
1395             return Finishings.NONE;
1396         } else if (category == JobName.class) {
1397             return new JobName("Java Printing", null);
1398         } else if (category == JobSheets.class) {
1399             if (attribClass != null &&
1400                 attribClass.getStringValue().equals("none")) {
1401                 return JobSheets.NONE;
1402             } else {
1403                 return JobSheets.STANDARD;
1404             }
1405         } else if (category == Media.class) {
1406             defaultMediaIndex = 0;
1407             if (mediaSizeNames.length == 0) {
1408                 String defaultCountry = Locale.getDefault().getCountry();
1409                 if (defaultCountry != null &&
1410                     (defaultCountry.equals("") ||
1411                      defaultCountry.equals(Locale.US.getCountry()) ||
1412                      defaultCountry.equals(Locale.CANADA.getCountry()))) {
1413                     return MediaSizeName.NA_LETTER;
1414                 } else {
1415                     return MediaSizeName.ISO_A4;
1416                 }
1417             }
1418 
1419             if (attribClass != null) {
1420                 String name = attribClass.getStringValue();
1421                 if (isCupsPrinter) {
1422                     for (int i=0; i< customMediaSizeNames.length; i++) {
1423                         //REMIND:  get default from PPD. In native _getMedia,
1424                         // move default (ppd_option_t->defchoice) to index 0.
1425                         // In the meantime, use indexOf because PPD name
1426                         // may be different from the IPP attribute name.
1427                         if (customMediaSizeNames[i].toString().indexOf(name)
1428                             != -1) {
1429                             defaultMediaIndex = i;
1430                             return mediaSizeNames[defaultMediaIndex];
1431                         }
1432                     }
1433                 } else {
1434                     for (int i=0; i< mediaSizeNames.length; i++) {
1435                         if (mediaSizeNames[i].toString().indexOf(name) != -1) {
1436                             defaultMediaIndex = i;
1437                             return mediaSizeNames[defaultMediaIndex];
1438                         }
1439                     }
1440                 }
1441             }
1442             return mediaSizeNames[defaultMediaIndex];
1443 
1444         } else if (category == MediaPrintableArea.class) {
1445             MediaPrintableArea[] mpas;
1446              if ((cps != null)  &&
1447                  ((mpas = cps.getMediaPrintableArea()) != null)) {
1448                  if (defaultMediaIndex == -1) {
1449                      // initializes value of defaultMediaIndex
1450                      getDefaultAttributeValue(Media.class);
1451                  }
1452                  return mpas[defaultMediaIndex];
1453              } else {
1454                  String defaultCountry = Locale.getDefault().getCountry();
1455                  float iw, ih;
1456                  if (defaultCountry != null &&
1457                      (defaultCountry.equals("") ||
1458                       defaultCountry.equals(Locale.US.getCountry()) ||
1459                       defaultCountry.equals(Locale.CANADA.getCountry()))) {
1460                      iw = MediaSize.NA.LETTER.getX(Size2DSyntax.INCH) - 0.5f;
1461                      ih = MediaSize.NA.LETTER.getY(Size2DSyntax.INCH) - 0.5f;
1462                  } else {
1463                      iw = MediaSize.ISO.A4.getX(Size2DSyntax.INCH) - 0.5f;
1464                      ih = MediaSize.ISO.A4.getY(Size2DSyntax.INCH) - 0.5f;
1465                  }
1466                  return new MediaPrintableArea(0.25f, 0.25f, iw, ih,
1467                                                MediaPrintableArea.INCH);
1468              }
1469         } else if (category == NumberUp.class) {
1470             return new NumberUp(1); // for CUPS this is always 1
1471         } else if (category == OrientationRequested.class) {
1472             if (attribClass != null) {
1473                 switch (attribClass.getIntValue()) {
1474                 default:
1475                 case 3: return OrientationRequested.PORTRAIT;
1476                 case 4: return OrientationRequested.LANDSCAPE;
1477                 case 5: return OrientationRequested.REVERSE_LANDSCAPE;
1478                 case 6: return OrientationRequested.REVERSE_PORTRAIT;
1479                 }
1480             } else {
1481                 return OrientationRequested.PORTRAIT;
1482             }
1483         } else if (category == PageRanges.class) {
1484             if (attribClass != null) {
1485                 int[] range = attribClass.getIntRangeValue();
1486                 return new PageRanges(range[0], range[1]);
1487             } else {
1488                 return new PageRanges(1, Integer.MAX_VALUE);
1489             }
1490         } else if (category == RequestingUserName.class) {
1491             String userName = "";
1492             try {
1493               userName = System.getProperty("user.name", "");
1494             } catch (SecurityException se) {
1495             }
1496             return new RequestingUserName(userName, null);
1497         } else if (category == SheetCollate.class) {
1498             return SheetCollate.UNCOLLATED;
1499         } else if (category == Sides.class) {
1500             if (attribClass != null) {
1501                 if (attribClass.getStringValue().endsWith("long-edge")) {
1502                     return Sides.TWO_SIDED_LONG_EDGE;
1503                 } else if (attribClass.getStringValue().endsWith(
1504                                                            "short-edge")) {
1505                     return Sides.TWO_SIDED_SHORT_EDGE;
1506                 }
1507             }
1508             return Sides.ONE_SIDED;
1509         }
1510 
1511         return null;
1512     }
1513 
1514     public ServiceUIFactory getServiceUIFactory() {
1515         return null;
1516     }
1517 
1518     public void wakeNotifier() {
1519         synchronized (this) {
1520             if (notifier != null) {
1521                 notifier.wake();
1522             }
1523         }
1524     }
1525 
1526     public void addPrintServiceAttributeListener(
1527                                  PrintServiceAttributeListener listener) {
1528         synchronized (this) {
1529             if (listener == null) {
1530                 return;
1531             }
1532             if (notifier == null) {
1533                 notifier = new ServiceNotifier(this);
1534             }
1535             notifier.addListener(listener);
1536         }
1537     }
1538 
1539     public void removePrintServiceAttributeListener(
1540                                   PrintServiceAttributeListener listener) {
1541         synchronized (this) {
1542             if (listener == null || notifier == null ) {
1543                 return;
1544             }
1545             notifier.removeListener(listener);
1546             if (notifier.isEmpty()) {
1547                 notifier.stopNotifier();
1548                 notifier = null;
1549             }
1550         }
1551     }
1552 
1553     public String getName() {
1554         return printer;
1555     }
1556 
1557 
1558     public boolean usesClass(Class c) {
1559         return (c == sun.print.PSPrinterJob.class);
1560     }
1561 
1562 
1563     public static HttpURLConnection getIPPConnection(URL url) {
1564         HttpURLConnection connection;
1565         try {
1566             connection = (HttpURLConnection)url.openConnection();
1567         } catch (java.io.IOException ioe) {
1568             return null;
1569         }
1570         if (!(connection instanceof HttpURLConnection)) {
1571             return null;
1572         }
1573         connection.setUseCaches(false);
1574         connection.setDefaultUseCaches(false);
1575         connection.setDoInput(true);
1576         connection.setDoOutput(true);
1577         connection.setRequestProperty("Content-type", "application/ipp");
1578         return connection;
1579     }
1580 
1581 
1582     public synchronized boolean isPostscript() {
1583         if (isPS == null) {
1584            isPS = Boolean.TRUE;
1585             if (isCupsPrinter) {
1586                 try {
1587                     urlConnection = getIPPConnection(
1588                                              new URL(myURL+".ppd"));
1589 
1590                    InputStream is = urlConnection.getInputStream();
1591                    if (is != null) {
1592                        BufferedReader d =
1593                            new BufferedReader(new InputStreamReader(is,
1594                                                           Charset.forName("ISO-8859-1")));
1595                        String lineStr;
1596                        while ((lineStr = d.readLine()) != null) {
1597                            if (lineStr.startsWith("*cupsFilter:")) {
1598                                isPS = Boolean.FALSE;
1599                                break;
1600                            }
1601                        }
1602                     }
1603                 } catch (java.io.IOException e) {
1604                     debug_println(" isPostscript, e= "+e);
1605                     /* if PPD is not found, this may be a raw printer
1606                        and in this case it is assumed that it is a
1607                        Postscript printer */
1608                     // do nothing
1609                 }
1610             }
1611         }
1612         return isPS.booleanValue();
1613     }
1614 
1615 
1616     private void opGetAttributes() {
1617         try {
1618             debug_println(debugPrefix+"opGetAttributes myURI "+myURI+" myURL "+myURL);
1619 
1620             AttributeClass attClNoUri[] = {
1621                 AttributeClass.ATTRIBUTES_CHARSET,
1622                 AttributeClass.ATTRIBUTES_NATURAL_LANGUAGE};
1623 
1624             AttributeClass attCl[] = {
1625                 AttributeClass.ATTRIBUTES_CHARSET,
1626                 AttributeClass.ATTRIBUTES_NATURAL_LANGUAGE,
1627                 new AttributeClass("printer-uri",
1628                                    AttributeClass.TAG_URI,
1629                                    ""+myURI)};
1630 
1631             OutputStream os = (OutputStream)java.security.AccessController.
1632                 doPrivileged(new java.security.PrivilegedAction() {
1633                     public Object run() {
1634                         try {
1635                             return urlConnection.getOutputStream();
1636                         } catch (Exception e) {
1637                         }
1638                         return null;
1639                     }
1640                 });
1641 
1642             if (os == null) {
1643                 return;
1644             }
1645 
1646             boolean success = (myURI == null) ?
1647                 writeIPPRequest(os, OP_GET_ATTRIBUTES, attClNoUri) :
1648                 writeIPPRequest(os, OP_GET_ATTRIBUTES, attCl);
1649             if (success) {
1650                 InputStream is = null;
1651                 if ((is = urlConnection.getInputStream())!=null) {
1652                     HashMap[] responseMap = readIPPResponse(is);
1653 
1654                     if (responseMap != null && responseMap.length > 0) {
1655                         getAttMap = responseMap[0];
1656                     }
1657                 } else {
1658                     debug_println(debugPrefix+"opGetAttributes - null input stream");
1659                 }
1660                 is.close();
1661             }
1662             os.close();
1663         } catch (java.io.IOException e) {
1664             debug_println(debugPrefix+"opGetAttributes - input/output stream: "+e);
1665         }
1666     }
1667 
1668 
1669     public static boolean writeIPPRequest(OutputStream os,
1670                                            String operCode,
1671                                            AttributeClass[] attCl) {
1672         OutputStreamWriter osw;
1673         try {
1674             osw = new OutputStreamWriter(os, "UTF-8");
1675         } catch (java.io.UnsupportedEncodingException exc) {
1676             debug_println(debugPrefix+"writeIPPRequest, UTF-8 not supported? Exception: "+exc);
1677             return false;
1678         }
1679         debug_println(debugPrefix+"writeIPPRequest, op code= "+operCode);
1680         char[] opCode =  new char[2];
1681         opCode[0] =  (char)Byte.parseByte(operCode.substring(0,2), 16);
1682         opCode[1] =  (char)Byte.parseByte(operCode.substring(2,4), 16);
1683         char[] bytes = {0x01, 0x01, 0x00, 0x01};
1684         try {
1685             osw.write(bytes, 0, 2); // version number
1686             osw.write(opCode, 0, 2); // operation code
1687             bytes[0] = 0x00; bytes[1] = 0x00;
1688             osw.write(bytes, 0, 4); // request ID #1
1689 
1690             bytes[0] = 0x01; // operation-group-tag
1691             osw.write(bytes[0]);
1692 
1693             String valStr;
1694             char[] lenStr;
1695 
1696             AttributeClass ac;
1697             for (int i=0; i < attCl.length; i++) {
1698                 ac = attCl[i];
1699                 osw.write(ac.getType()); // value tag
1700 
1701                 lenStr = ac.getLenChars();
1702                 osw.write(lenStr, 0, 2); // length
1703                 osw.write(""+ac, 0, ac.getName().length());
1704 
1705                 // check if string range (0x35 -> 0x49)
1706                 if (ac.getType() >= AttributeClass.TAG_TEXT_LANGUAGE &&
1707                     ac.getType() <= AttributeClass.TAG_MIME_MEDIATYPE){
1708                     valStr = (String)ac.getObjectValue();
1709                     bytes[0] = 0; bytes[1] = (char)valStr.length();
1710                     osw.write(bytes, 0, 2);
1711                     osw.write(valStr, 0, valStr.length());
1712                 } // REMIND: need to support other value tags but for CUPS
1713                 // string is all we need.
1714             }
1715 
1716             osw.write(GRPTAG_END_ATTRIBUTES);
1717             osw.flush();
1718             osw.close();
1719         } catch (java.io.IOException ioe) {
1720             debug_println(debugPrefix+"writeIPPRequest, IPPPrintService Exception in writeIPPRequest: "+ioe);
1721             return false;
1722         }
1723         return true;
1724     }
1725 
1726 
1727     public static HashMap[] readIPPResponse(InputStream inputStream) {
1728 
1729         if (inputStream == null) {
1730             return null;
1731         }
1732 
1733         byte response[] = new byte[MAX_ATTRIBUTE_LENGTH];
1734         try {
1735 
1736             DataInputStream ois = new DataInputStream(inputStream);
1737 
1738             // read status and ID
1739             if ((ois.read(response, 0, 8) > -1) &&
1740                 (response[2] == STATUSCODE_SUCCESS)) {
1741 
1742                 ByteArrayOutputStream outObj;
1743                 int counter=0;
1744                 short len = 0;
1745                 String attribStr = null;
1746                 // assign default value
1747                 byte valTagByte = AttributeClass.TAG_KEYWORD;
1748                 ArrayList respList = new ArrayList();
1749                 HashMap responseMap = new HashMap();
1750 
1751                 response[0] = ois.readByte();
1752 
1753                 // check for group tags
1754                 while ((response[0] >= GRPTAG_OP_ATTRIBUTES) &&
1755                        (response[0] <= GRPTAG_PRINTER_ATTRIBUTES)
1756                           && (response[0] != GRPTAG_END_ATTRIBUTES)) {
1757                     debug_println(debugPrefix+"readIPPResponse, checking group tag,  response[0]= "+
1758                                   response[0]);
1759 
1760                     outObj = new ByteArrayOutputStream();
1761                     //make sure counter and attribStr are re-initialized
1762                     counter = 0;
1763                     attribStr = null;
1764 
1765                     // read value tag
1766                     response[0] = ois.readByte();
1767                     while (response[0] >= AttributeClass.TAG_UNSUPPORTED_VALUE &&
1768                            response[0] <= AttributeClass.TAG_MEMBER_ATTRNAME) {
1769                         // read name length
1770                         len  = ois.readShort();
1771 
1772                         // If current value is not part of previous attribute
1773                         // then close stream and add it to HashMap.
1774                         // It is part of previous attribute if name length=0.
1775                         if ((len != 0) && (attribStr != null)) {
1776                             //last byte is the total # of values
1777                             outObj.write(counter);
1778                             outObj.flush();
1779                             outObj.close();
1780                             byte outArray[] = outObj.toByteArray();
1781 
1782                             // if key exists, new HashMap
1783                             if (responseMap.containsKey(attribStr)) {
1784                                 respList.add(responseMap);
1785                                 responseMap = new HashMap();
1786                             }
1787 
1788                             // exclude those that are unknown
1789                             if (valTagByte >= AttributeClass.TAG_INT) {
1790                                 AttributeClass ac =
1791                                     new AttributeClass(attribStr,
1792                                                        valTagByte,
1793                                                        outArray);
1794 
1795                                 responseMap.put(ac.getName(), ac);
1796                                 debug_println(debugPrefix+ "readIPPResponse "+ac);
1797                             }
1798 
1799                             outObj = new ByteArrayOutputStream();
1800                             counter = 0; //reset counter
1801                         }
1802                         //check if this is new value tag
1803                         if (counter == 0) {
1804                             valTagByte = response[0];
1805                         }
1806                         // read attribute name
1807                         if (len != 0) {
1808                             // read "len" characters
1809                             // make sure it doesn't exceed the maximum
1810                             if (len > MAX_ATTRIBUTE_LENGTH) {
1811                                 response = new byte[len]; // expand as needed
1812                             }
1813                             ois.read(response, 0, len);
1814                             attribStr = new String(response, 0, len);
1815                         }
1816                         // read value length
1817                         len  = ois.readShort();
1818                         // write name length
1819                         outObj.write(len);
1820                         // read value, make sure it doesn't exceed the maximum
1821                         if (len > MAX_ATTRIBUTE_LENGTH) {
1822                             response = new byte[len]; // expand as needed
1823                         }
1824                         ois.read(response, 0, len);
1825                         // write value of "len" length
1826                         outObj.write(response, 0, len);
1827                         counter++;
1828                         // read next byte
1829                         response[0] = ois.readByte();
1830                     }
1831 
1832                     if (attribStr != null) {
1833                         outObj.write(counter);
1834                         outObj.flush();
1835                         outObj.close();
1836 
1837                         // if key exists in old HashMap, new HashMap
1838                         if ((counter != 0) &&
1839                             responseMap.containsKey(attribStr)) {
1840                             respList.add(responseMap);
1841                             responseMap = new HashMap();
1842                         }
1843 
1844                         byte outArray[] = outObj.toByteArray();
1845 
1846                         AttributeClass ac =
1847                             new AttributeClass(attribStr,
1848                                                valTagByte,
1849                                                outArray);
1850                         responseMap.put(ac.getName(), ac);
1851                     }
1852                 }
1853                 ois.close();
1854                 if ((responseMap != null) && (responseMap.size() > 0)) {
1855                     respList.add(responseMap);
1856                 }
1857                 return (HashMap[])respList.toArray(
1858                                   new HashMap[respList.size()]);
1859             } else {
1860                 debug_println(debugPrefix+
1861                               "readIPPResponse client error, IPP status code-"
1862                                    +Integer.toHexString(response[2])+" & "
1863                                    +Integer.toHexString(response[3]));
1864                 return null;
1865             }
1866 
1867         } catch (java.io.IOException e) {
1868             debug_println(debugPrefix+"readIPPResponse: "+e);
1869             if (debugPrint) {
1870                 e.printStackTrace();
1871             }
1872             return null;
1873         }
1874     }
1875 
1876 
1877     public String toString() {
1878         return "IPP Printer : " + getName();
1879     }
1880 
1881     public boolean equals(Object obj) {
1882         return  (obj == this ||
1883                  (obj instanceof IPPPrintService &&
1884                   ((IPPPrintService)obj).getName().equals(getName())));
1885     }
1886 
1887     public int hashCode() {
1888         return this.getClass().hashCode()+getName().hashCode();
1889     }
1890 }